(function(window, undefined) {
	"use strict";
	var hasTouch = ("createTouch" in document) || ('ontouchstart' in window),
	testStyle = document.createElement('div').style,
	testVendor = (function() {
		var cases = {
			'OTransform': ['-o-', 'otransitionend'],
			'WebkitTransform': ['-webkit-', 'webkitTransitionEnd'],
			'MozTransform': ['-moz-', 'transitionend'],
			'msTransform': ['-ms-', 'MSTransitionEnd'],
			'transform': ['', 'transitionend']
		},
		prop;
		for (prop in cases) {
			if (prop in testStyle) return cases[prop];
		}
		return false;
	})(),
	sg = [['width', 'left', 'right'], ['height', 'top', 'bottom']],
	cssVendor = testVendor && testVendor[0],
	toCase = function(str) {
		return (str + '').replace(/^-ms-/, 'ms-').replace(/-([a-z]|[0-9])/ig,
		function(all, letter) {
			return (letter + '').toUpperCase();
		});
	},
	testCSS = function(prop) {
		var _prop = toCase(cssVendor + prop);
		return (prop in testStyle) && prop || (_prop in testStyle) && _prop;
	},
	parseArgs = function(arg, dft) {
		for (var key in dft) {
			if (typeof arg[key] == 'undefined') {
				arg[key] = dft[key];
			}
		}
		return arg;
	},
	children = function(elem) {
		var children = elem.children || elem.childNodes,
		_ret = [],
		i = 0;
		for (; i < children.length; i++) {
			if (children[i].nodeType === 1) {
				_ret.push(children[i]);
			}
		}
		return _ret;
	},
	each = function(arr, func) {
		var i = 0,
		j = arr.length;
		for (; i < j; i++) {
			if (func.call(arr[i], i, arr[i]) === false) {
				break;
			}
		}
	},
	returnFalse = function(evt) {
		evt = TouchSlider.fn.eventHook(evt);
		evt.preventDefault();
	},
	startEvent = hasTouch ? "touchstart": "mousedown",
	moveEvent = hasTouch ? "touchmove": "mousemove",
	endEvent = hasTouch ? "touchend": "mouseup",
	transitionend = testVendor[1] || '',
	TouchSlider = function(id, cfg) {
		if (! (this instanceof TouchSlider)) {
			return new TouchSlider(id, cfg);
		}
		if (typeof id != 'string' && !id.nodeType) {
			cfg = id;
			id = cfg.id;
		}
		if (!id.nodeType) {
			id = document.getElementById(id);
		}
		this.cfg = parseArgs(cfg || {},
		this._default);
		this.element = id;
		if (this.element) {
			this.container = this.element.parentNode || document.body;
			this.setup();
		}
	}
	TouchSlider.fn = TouchSlider.prototype = {
		_default: {
			'id': 'slider',
			'begin': 0,
			'auto': true,
			'speed': 600,
			'timeout': 5000,
			'direction': 'left',
			'align': 'center',
			'fixWidth': true,
			'mouseWheel': false,
			'before': new Function,
			'after': new Function
		},
		css: function(elem, css) {
			if (typeof css == 'string') {
				var style = document.defaultView && document.defaultView.getComputedStyle && getComputedStyle(elem, null) || elem.currentStyle || elem.style || {};
				return style[toCase(css)];
			} else {
				var prop,
				propFix;
				for (prop in css) {
					if (prop == 'float') {
						propFix = ("cssFloat" in testStyle) ? 'cssFloat': 'styleFloat';
					} else {
						propFix = toCase(prop);
					}
					elem.style[propFix] = css[prop];
				}
			}
		},
		addListener: function(e, n, o, u) {
			if (e.addEventListener) {
				e.addEventListener(n, o, u);
				return true;
			} else if (e.attachEvent) {
				e.attachEvent('on' + n, o);
				return true;
			}
			return false;
		},
		removeListener: function(e, n, o, u) {
			if (e.addEventListener) {
				e.removeEventListener(n, o, u);
				return true;
			} else if (e.attachEvent) {
				e.detachEvent('on' + n, o);
				return true;
			}
			return false;
		},
		eventHook: function(origEvt) {
			var evt = {},
			props = "changedTouches touches scale target view which clientX clientY fromElement offsetX offsetY pageX pageY toElement".split(" ");
			origEvt = origEvt || window.event;
			each(props,
			function() {
				evt[this] = origEvt[this];
			});
			evt.target = origEvt.target || origEvt.srcElement || document;
			if (evt.target.nodeType === 3) {
				evt.target = evt.target.parentNode;
			}

			evt.preventDefault = function() {
				origEvt.preventDefault && origEvt.preventDefault();
				evt.returnValue = origEvt.returnValue = false;
			}
			evt.stopPropagation = function() {
				origEvt.stopPropagation && origEvt.stopPropagation();
				evt.cancelBubble = origEvt.cancelBubble = true;
			}
			if (hasTouch && evt.touches.length) {
				evt.pageX = evt.touches.item(0).pageX;
				evt.pageY = evt.touches.item(0).pageY;
			} else if (typeof origEvt.pageX == 'undefined') {
				var doc = document.documentElement,
				body = document.body;
				evt.pageX = origEvt.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
				evt.pageY = origEvt.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0);
			}
			evt.origEvent = origEvt;
			return evt;
		},
		bind: function(func, obj) {
			return function() {
				return func.apply(obj, arguments);
			}
		},
		setup: function() {
			this.slides = children(this.element);
			this.length = this.slides.length;
			this.cfg.timeout = parseInt(this.cfg.timeout);
			this.cfg.speed = parseInt(this.cfg.speed);
			this.cfg.begin = parseInt(this.cfg.begin);
			this.cfg.auto = !!this.cfg.auto;
			this.cfg.timeout = Math.max(this.cfg.timeout, this.cfg.speed);
			this.touching = !!hasTouch;
			this.css3transition = !!testVendor;
			this.index = this.cfg.begin < 0 || this.cfg.begin >= this.length ? 0: this.cfg.begin;
			if (this.length < 1) return false;
			switch (this.cfg.direction) {
			case 'up':
			case 'down':
				this.direction = this.cfg.direction;
				this.vertical = 1;
				break;
			case 'right':
				this.direction = 'right';
			default:
				this.direction = this.direction || 'left';
				this.vertical = 0;
				break;
			}
			this.addListener(this.element, startEvent, this.bind(this._start, this), false);
			this.addListener(document, moveEvent, this.bind(this._move, this), false);
			this.addListener(document, endEvent, this.bind(this._end, this), false);
			this.addListener(document, 'touchcancel', this.bind(this._end, this), false);
			this.addListener(this.element, transitionend, this.bind(this.transitionend, this), false);
			this.addListener(window, 'resize', this.bind(function() {
				clearTimeout(this.resizeTimer);
				this.resizeTimer = setTimeout(this.bind(this.resize, this), 100);
			},
			this), false);
			if (this.cfg.mouseWheel) {
				this.addListener(this.element, 'mousewheel', this.bind(this.mouseScroll, this), false);
				this.addListener(this.element, 'DOMMouseScroll', this.bind(this.mouseScroll, this), false);
			}
			this.playing = this.cfg.auto;
			this.resize();
		},
		getSum: function(type, start, end) {
			var sum = 0,
			i = start,
			_type = toCase('-' + type);
			for (; i < end; i++) {
				sum += this['getOuter' + _type](this.slides[i]);
			}
			return sum;
		},
		getPos: function(type, index) {
			var _type = toCase('-' + type),
			myWidth = this.getSum(type, index, index + 1),
			sum = this.getSum(type, 0, index) + this['getOuter' + _type](this.element) / 2 - this['get' + _type](this.element) / 2;
			switch (this.cfg.align) {
			case 'left':
				return - sum;
			case 'right':
				return this[type] - myWidth - sum;
			default:
				return (this[type] - myWidth) / 2 - sum;
			}
		},
		resize: function() {
			clearTimeout(this.aniTimer);
			var _this = this,
			css,
			type = sg[this.vertical][0],
			_type = toCase('-' + type),
			pst = this.css(this.container, 'position');
			this.css(this.container, {
				'overflow': 'hidden',
				'visibility': 'hidden',
				'listStyle': 'none',
				'position': pst == 'static' ? 'relative': pst
			});
			this[type] = this['get' + _type](this.container);
			css = {
				float: this.vertical ? 'none': 'left',
				display: 'block'
			};
			each(this.slides,
			function() {
				if (_this.cfg.fixWidth) {
					css[type] = _this[type] - _this['margin' + _type](this) - _this['padding' + _type](this) - _this['border' + _type](this) + 'px';
				}
				_this.css(this, css);
			});
			this.total = this.getSum(type, 0, this.length);
			css = {
				position: 'relative',
				overflow: 'hidden'
			};
			css[cssVendor + 'transition-duration'] = '0ms';
			css[type] = this.total + 'px';
			css[sg[this.vertical][1]] = this.getPos(type, this.index) + 'px';
			this.css(this.element, css);
			this.css(this.container, {
				'visibility': 'visible'
			});
			this.playing && this.play();
			return this;
		},
		slide: function(index, speed) {
			var direction = sg[this.vertical][1],
			type = sg[this.vertical][0],
			transition = testCSS('transition'),
			nowPos = parseFloat(this.css(this.element, direction)) || 0,
			endPos,
			css = {},
			change,
			size = this.getSum(type, index, index + 1);
			index = Math.min(Math.max(0, index), this.length - 1);
			speed = typeof speed == 'undefined' ? this.cfg.speed: parseInt(speed);
			endPos = this.getPos(type, index);
			change = endPos - nowPos,
			speed = Math.abs(change) < size ? Math.ceil(Math.abs(change) / size * speed) : speed;
			if (transition) {
				css[transition] = direction + ' ease ' + speed + 'ms';
				css[direction] = endPos + 'px';
				this.css(this.element, css);
			} else {
				var _this = this,
				begin = 0,
				time = speed / 10,
				animate = function(t, b, c, d) {
					return - c * ((t = t / d - 1) * t * t * t - 1) + b;
				},
				run = function() {
					if (begin < time) {
						begin++;
						_this.element.style[direction] = Math.ceil(animate(begin, nowPos, change, time)) + 'px';
						_this.aniTimer = setTimeout(run, 10);
					} else {
						_this.element.style[direction] = endPos + 'px';
						_this.transitionend({
							propertyName: direction
						});
					}
				}
				clearTimeout(this.aniTimer);
				run();
			}
			this.cfg.before.call(this, index, this.slides[this.index]);
			this.index = index;
			return this;
		},
		play: function() {
			clearTimeout(this.timer);
			this.playing = true;
			this.timer = setTimeout(this.bind(function() {
				this.direction == 'left' || this.direction == 'up' ? this.next() : this.prev();
			},
			this), this.cfg.timeout);
			return this;
		},
		pause: function() {
			clearTimeout(this.timer);
			this.playing = false;
			return this;
		},
		stop: function() {
			this.pause();
			return this.slide(0);
		},
		prev: function(offset, sync) {
			clearTimeout(this.timer);
			var index = this.index;
			offset = typeof offset == 'undefined' ? offset = 1: offset % this.length;
			index -= offset;
			if (sync === false) {
				index = Math.max(index, 0);
			} else {
				index = index < 0 ? this.length + index: index;
			}
			return this.slide(index);
		},
		next: function(offset, sync) {
			clearTimeout(this.timer);
			var index = this.index;
			if (typeof offset == 'undefined') offset = 1;
			index += offset;
			if (sync === false) {
				index = Math.min(index, this.length - 1);
			} else {
				index %= this.length
			}
			return this.slide(index);
		},
		_start: function(evt) {
			evt = this.eventHook(evt);
			if (!this.touching) evt.preventDefault();
			this.removeListener(this.element, 'click', returnFalse);
			this.startPos = [evt.pageX, evt.pageY];
			this.element.style[toCase(cssVendor + 'transition-duration')] = '0ms';
			this.startTime = +new Date;
			this._pos = parseFloat(this.css(this.element, sg[this.vertical][1])) || 0;
		},
		_move: function(evt) {
			if (!this.startPos || evt.scale && evt.scale !== 1) return;
			evt = this.eventHook(evt);
			this.stopPos = [evt.pageX, evt.pageY];
			var direction = sg[this.vertical][1],
			type = sg[this.vertical][0],
			offset = this.stopPos[this.vertical] - this.startPos[this.vertical];
			if (this.scrolling || typeof this.scrolling == 'undefined' && Math.abs(offset) >= Math.abs(this.stopPos[1 - this.vertical] - this.startPos[1 - this.vertical])) {
				evt.preventDefault();
				offset = offset / ((!this.index && offset > 0 || this.index == this.length - 1 && offset < 0) ? (Math.abs(offset) / this[type] + 1) : 1);
				this.element.style[direction] = this._pos + offset + 'px';
				if (offset && typeof this.scrolling == 'undefined') {
					this.scrolling = true;
					clearTimeout(this.timer);
					clearTimeout(this.aniTimer);
				}
			} else this.scrolling = false;
		},
		_end: function(evt) {
			if (this.startPos) {
				if (this.scrolling) {
					var type = sg[this.vertical][0],
					direction = sg[this.vertical][1],
					offset = this.stopPos[this.vertical] - this.startPos[this.vertical],
					absOff = Math.abs(offset),
					sub = absOff / offset,
					myWidth,
					curPos,
					tarPos,
					next = this.index,
					off = 0;
					this.addListener(this.element, 'click', returnFalse);
					if (absOff > 20) {
						curPos = parseFloat(this.css(this.element, sg[this.vertical][1]));
						do {
							if (next >= 0 && next < this.length) {
								tarPos = this.getPos(type, next);
								myWidth = this.getSum(type, next, next + 1);
							} else {
								next += sub;
								break;
							}
						}
						while (Math.abs(tarPos - curPos) > myWidth / 2 && (next -= sub));
						off = Math.abs(next - this.index);
						if (!off && +new Date - this.startTime < 250) {
							off = 1;
						}
					}
					offset > 0 ? this.prev(off, false) : this.next(off, false);
					this.playing && this.play();
				}
				delete this._pos;
				delete this.stopPos;
				delete this.startPos;
				delete this.scrolling;
				delete this.startTime;
			}
		},
		mouseScroll: function(evt) {
			if (this.cfg.mouseWheel) {
				evt = this.eventHook(evt);
				evt.preventDefault();
				var _e = evt.origEvent;
				var wheelDelta = _e.wheelDelta || _e.detail && _e.detail * -1 || 0,
				flag = wheelDelta / Math.abs(wheelDelta);
				wheelDelta > 0 ? this.prev(1, false) : this.next(1, false);
			}
		},
		transitionend: function(evt) {
			if (evt.propertyName == sg[this.vertical][1]) {
				this.cfg.after.call(this, this.index, this.slides[this.index]);
				this.playing && this.play();
			}
		}
	}
	each(['Width', 'Height'],
	function(i, type) {
		var _type = type.toLowerCase();
		each(['margin', 'padding', 'border'],
		function(j, name) {
			TouchSlider.fn[name + type] = function(elem) {
				return parseFloat(this.css(elem, name + '-' + sg[i][1] + (name == 'border' ? '-width': ''))) + parseFloat(this.css(elem, name + '-' + sg[i][2] + (name == 'border' ? '-width': '')));
			}
		});
		TouchSlider.fn['get' + type] = function(elem) {
			return elem['offset' + type] - this['padding' + type](elem) - this['border' + type](elem);
		}
		TouchSlider.fn['getOuter' + type] = function(elem) {
			return elem['offset' + type] + this['margin' + type](elem);
		}
	});
	window.TouchSlider = TouchSlider;
})(window);